#include "aerHeightmap.hpp"
#include <glm/gtc/noise.hpp>


namespace aer {


Heightmap::Heightmap()
  : m_vertices(NULL),
    m_width(0u),
    m_height(0u)
{
}

Heightmap::~Heightmap()
{
  AER_SAFE_DELETEV( m_vertices );
}
   
bool Heightmap::generate(const U32 width, const U32 height, const Vector3 &scales)
{
  const float kFrequency = 0.015f;//
  
  m_width = width;
  m_height = height;
  
  m_vertices = new HMVertex[ m_width * m_height ];//  
  AER_CHECK( m_vertices != NULL );  
    
  // Generates the height values
  for (U32 j=0u, index=0u; j<height; ++j) 
  {
    for (U32 i=0u; i<width; ++i, ++index) 
    {
      Vector2 v(i, j);
      
      float h = glm::perlin( kFrequency * v ); // value in ~ [-.5f, .5f]
      m_vertices[index].position.y = scales.y * (h+.5);
    }
  }
  
  float di = 1.0f/(width-1.0f);
  float dj = 1.0f/(height-1.0f);
  
  // Generates the full attributes
  for (U32 j=0u, index=0u; j<height; ++j) 
  {   
    for (U32 i=0u; i<width; ++i, ++index)
    {
      HMVertex &vertex = m_vertices[index]; 
      Vector2 uv( i*di, j*dj);
      
      // Position
      vertex.position.x = scales.x * uv.x;
      vertex.position.z = scales.z * uv.y;
      
      // TexCoord
      vertex.texcoord = uv;      
    }
  }
  
  computeNBT();
}

void Heightmap::computeNBT()
{
  Vector3 topleft;
  Vector3 top;
  Vector3 left;  
  Vector3 bottomright;
  Vector3 bottom;
  Vector3 right;
  
  Vector3 normals[6];
  Vector3 v1, v2;
  
  U32 index = 0u;
  for (U32 y=0u; y<m_height; ++y) 
  {    
    U32 y1 = (y > 0)          ? y-1 : 0;
    U32 y2 = (y < m_height-1) ? y+1 : m_height-1;
    
    for (U32 x=0u; x<m_width; ++x, ++index)
    {
      U32 x1 = (x > 0)         ? x-1 : 0;
      U32 x2 = (x < m_width-1) ? x+1 : m_width-1;      
      
      HMVertex &vertex = m_vertices[index];
      
      top         = m_vertices[y1*m_width + x].position;
      bottom      = m_vertices[y2*m_width + x].position;
      left        = m_vertices[y*m_width + x1].position;
      right       = m_vertices[y*m_width + x2].position;      
      /*
      topleft     = m_vertices[y1*m_width + x1].position;
      bottomright = m_vertices[y2*m_width + x2].position;
      
      
      v1 = left - vertex.position;
      v2 = topleft - vertex.position;
      normals[0] = glm::cross( v1, v2);
      
      v1 = topleft - vertex.position;
      v2 = top - vertex.position;
      normals[1] = glm::cross( v1, v2);
      
      v1 = top - vertex.position;
      v2 = right - vertex.position;
      normals[2] = glm::cross( v1, v2);
      
      v1 = right - vertex.position;
      v2 = bottomright - vertex.position;
      normals[3] = glm::cross( v1, v2);
      
      v1 = bottomright - vertex.position;
      v2 = bottom - vertex.position;
      normals[4] = glm::cross( v1, v2);
      
      v1 = bottom - vertex.position;
      v2 = left - vertex.position;
      normals[5] = glm::cross( v1, v2);
      
      normals[0] += normals[1] + normals[2] + normals[3] + normals[4] + normals[5];      
      
      // Normal
      vertex.normal = - glm::normalize( normals[0] );      
      */
      
      vertex.normal = glm::normalize(glm::cross( right-left, bottom-top));
      vertex.normal.y *= -1.0f;//
      
      
      // Binormal
      vertex.tangent = right - vertex.position;            
      vertex.binormal = glm::cross( vertex.tangent, vertex.normal );
      vertex.binormal = glm::normalize( vertex.binormal );
      
      // Tangent
      vertex.tangent = glm::cross( vertex.normal, vertex.binormal);
      vertex.tangent  = glm::normalize( vertex.tangent );  
    }
  }
}


float Heightmap::getLocationHeight(const Vector2 &location) const
{
  AER_ASSERT( "Heightmap not generated" && m_vertices );
  
  U32 x0, y0;
  U32 x1, y1;
  
  Vector2 bound( m_vertices[m_height*m_width-1u].position.x,
                 m_vertices[m_height*m_width-1u].position.z );
                 
  x0 = m_width  * (location.x / bound.x);
  y0 = m_height * (location.y / bound.y);
  x0 = glm::clamp( x0, 0u, m_width-2);
  y0 = glm::clamp( y0, 0u, m_height-2);
  
  
  x1 = x0 + 1;
  y1 = y0 + 1;
  
  float fx = (location.x - m_vertices[y0*m_width + x0].position.x) / bound.x;
  float fy = (location.y - m_vertices[y0*m_width + x0].position.y) / bound.y;
              
  float h1 = (1.0f-fx)*m_vertices[y0*m_width + x0].position.y + fx*m_vertices[y0*m_width + x1].position.y;
  float h2 = (1.0f-fx)*m_vertices[y1*m_width + x0].position.y + fx*m_vertices[y1*m_width + x1].position.y;
  float height = (1.0f-fy)*h1 + fy*h2;
  
  return height;
}

} // aer
